/*
 * Copyright (C) 2015-2019 Alibaba Group Holding Limited
 */

#ifndef CANOPEN__H_
#define CANOPEN__H_

#include <stdint.h>
#include <stdbool.h>
#include "canopen/co_errno.h"

#define CANOPEN_MASTER 1
#define CANOPEN_SLAVE  0

#define CO_CFG_NODE_SIZE 12

#define MIN_HEART_BEAT_CYCLE 100 /* ms */

typedef uint8_t co_event_t;

#define co_event_schedule     63
#define co_event_user_base    64

typedef void (*schedule_user_cb)(const uint8_t len, const void *param, void *user_data);

/* currently AliOS Things CANopen support nmt_control_service_reset, nmt_control_service_start,
   as master, may you can send it out, but no impact on components where AliOS Things CANopen running  */
typedef enum {
    nmt_control_service_start      = 1,
    nmt_control_service_stop       = 2,
    nmt_control_service_enter_pre  = 128,
    nmt_control_service_reset      = 129,
    nmt_control_service_reset_comm = 130
} nmt_control_service_t;

typedef enum {
    nmt_state_init            = 0,
    /* nmt_state_disconnected, */
    /* nmt_state_connecting,   */
    /* nmt_state_preparing,    */
    nmt_state_stopped          = 4,
    nmt_state_operational,
	/* non DS301 Standard, extend for Upgrade */
    nmt_state_upgrade          = 6,
    /* non DS301 Standard, extend for Upgrade */
    nmt_state_upgrade_base     = 10,
    nmt_state_pre_operational  = 127
} nmt_state_t;

typedef enum {
    co_sdo_role_client,
    co_sdo_role_server,
} co_sdo_role_t;

typedef enum {
    pdo_idx_emergency = 0,
    pdo_idx_1,
    pdo_idx_2,
    pdo_idx_3,
    pdo_idx_4,
    pdo_idx_size
} pdo_idx_type_t;

typedef enum {
    co_nmt_node_heart_beat,
    co_nmt_node_guarding
} co_nmt_node_link_t;

typedef struct {
    uint16_t sdo_timeout;          /* unit is ms */
    uint8_t  enable_link_maintain; /* 0 indicates not active link maintain, others is reverse */
} canopen_config_t;

typedef struct {
    uint8_t       port;
    uint8_t       node;
} can_node_id_t;

typedef struct {
    uint8_t        len;
    uint8_t        start_byte;
    void          *data;
} pdo_tx_set_t;

typedef struct {
    uint8_t        port;
    uint8_t        node;
    pdo_idx_type_t idx;
    uint8_t        len;
    uint8_t        data[8];
} pdo_rx_cb_para_t;

/* len, data: modified data; recorded: If new data has been recored into OD object, if true the data has been recorded,
    if false(usually the Profile/App Layler always take the entry mutex ) the Profile/App Layer shall considring handling it by self */
typedef void (*sdo_modify_cb_t)(const uint16_t len, const void *data, const bool recorded, void *user_data);

typedef struct {
    uint16_t        index;
    uint8_t         sub_index;
    uint8_t         attr;           /* @ co_sdo_attr_mask_t */
    uint16_t        size;
    uint16_t        actual_data_num;
    void           *object_data;
    sdo_modify_cb_t cb;
    void           *user_data;
} od_entry_t;

typedef void (*node_state_change_cb_t)(const can_node_id_t *node, const nmt_state_t state, void *user_data);

typedef void (*pdo_rx_cb_t)(const pdo_rx_cb_para_t *rx_data, void *user_data);

#ifdef __cplusplus
extern "C" {
#endif
/* Common APIs for both CANopen master and Components */
int32_t canopen_init(const uint8_t port, const uint8_t role, const canopen_config_t *config);

int32_t co_event_notify(const uint8_t port, const co_event_t type, const uint8_t len, const void *param);
int32_t co_schedule_register_user(const uint8_t port, const co_event_t type, schedule_user_cb cb, void *user_data);

int32_t node_install(const uint8_t port, const uint8_t node_cnt, const uint8_t *node_list, node_state_change_cb_t state_change_cb, void *user_data);
can_node_id_t *foreach_can_node(const uint8_t port, uint8_t *start);

int32_t pdo_register_cb(const uint8_t port, const uint8_t node, pdo_rx_cb_t cb, void *user_data);
int32_t pdo_set(const uint8_t port, const uint8_t node, const pdo_idx_type_t idx, pdo_tx_set_t *param, const bool send);
int32_t pdo_send(const uint8_t port, const uint8_t node, const pdo_idx_type_t idx);

int32_t sdo_install(const uint8_t port, const uint8_t node, const co_sdo_role_t role, od_entry_t *od, const uint8_t od_cnt);

/* APIs for CANopen master operate on SDO */
int32_t co_sdo_write_od(const can_node_id_t *node, const uint16_t idx, const uint8_t sub_idx, const void *data, const uint16_t len);
int32_t co_sdo_read_od(const can_node_id_t *node, const uint16_t idx, const uint8_t sub_idx, const void *buffer, const uint16_t buffer_len);
int32_t co_upgrade_start(const can_node_id_t *node, const bool start);

int32_t co_upgrade_recv(const can_node_id_t *node, const uint16_t buffer_len, uint8_t *buffer, const uint32_t ms);

/* APIs for CANopen Components operate on SDO */
int32_t co_sdo_set_od(const can_node_id_t *node, const uint16_t idx, const uint8_t sub_idx, const void *data, const uint16_t len);
int32_t co_upgrade_report(const can_node_id_t *node, const uint8_t response);
/* REMARK: This is not thread safety if cusomer try to modify both call-back and user-data, please call this function in init process if you try to register both of them */
int32_t co_sdo_register_modify_event(const can_node_id_t *node_id, const uint16_t idx, const uint16_t sub_idx, sdo_modify_cb_t cb, void *user_data);

/* APIs for Node Manager */
int32_t co_node_enter_operation(const uint8_t port, const uint8_t node);

int32_t co_node_reset_all(const uint8_t port);

int32_t co_node_lost_connect(const uint8_t port, const uint8_t node);


/*  called by component when try to send out boot up message  */
int32_t comp_boot_up_finish(const uint8_t port, const uint8_t node);

#ifdef __cplusplus
}
#endif

#endif /* CANOPEN__H_ */
